﻿namespace Resharper.Abstractions.Extensions.DocumentationExtensions
{
  using System;
  using System.Collections.Generic;
  using System.Linq;
  using System.Xml;
  using JetBrains.Annotations;
  using JetBrains.ReSharper.Psi.Tree;
  using Resharper.Abstractions.CodeDom;
  using Resharper.Abstractions.CodeDom.TreeNodes;
  using Resharper.Abstractions.Extensions.XmlNodeExtensions;

  /// <summary>Defines the <see cref="DocumentedExceptionExtensions"/> class.</summary>
  public static class DocumentedExceptionExtensions
  {
    #region Public Methods

    /// <summary>Gets the invocation exceptions.</summary>
    /// <param name="treeNode">The declared element.</param>
    /// <returns>Returns the documented exceptions.</returns>
    public static IEnumerable<DocumentedException> DocumentedExceptions([NotNull] this TreeNode treeNode)
    {
      if (treeNode == null)
      {
        throw new ArgumentNullException("treeNode");
      }

      var declaration = treeNode.InternalTreeNode as IDeclaration;
      if (declaration == null)
      {
        return Enumerable.Empty<DocumentedException>();
      }

      var xmlNode = declaration.GetXMLDoc(true);
      if (xmlNode == null)
      {
        return Enumerable.Empty<DocumentedException>();
      }

      return GetDocumentedException(xmlNode);
    }

    /// <summary>Documenteds the exceptions.</summary>
    /// <param name="declaredElement">The tree node.</param>
    /// <returns>Returns the exceptions.</returns>
    public static IEnumerable<DocumentedException> DocumentedExceptions([NotNull] this DeclaredElement declaredElement)
    {
      if (declaredElement == null)
      {
        throw new ArgumentNullException("declaredElement");
      }

      var declaration = declaredElement.InternalDeclaredElement;
      if (declaration == null)
      {
        return Enumerable.Empty<DocumentedException>();
      }

      var xmlNode = declaration.GetXMLDoc(true);
      if (xmlNode == null)
      {
        return Enumerable.Empty<DocumentedException>();
      }

      return GetDocumentedException(xmlNode);
    }

    #endregion

    #region Methods

    /// <summary>Gets the documented exception.</summary>
    /// <param name="xmlNode">The XML node.</param>
    /// <returns>Returns the documented exception.</returns>
    private static IEnumerable<DocumentedException> GetDocumentedException(XmlNode xmlNode)
    {
      var thrownExceptions = xmlNode.SelectNodes("exception");
      if (thrownExceptions == null || thrownExceptions.Count == 0)
      {
        yield break;
      }

      foreach (XmlNode node in thrownExceptions)
      {
        var typeName = node.GetAttributeString("cref");
        if (string.IsNullOrEmpty(typeName))
        {
          continue;
        }

        if (typeName.StartsWith("T:"))
        {
          typeName = typeName.Substring(2);
        }

        if (typeName.StartsWith("!:"))
        {
          typeName = typeName.Substring(2);
        }

        typeName = typeName.Replace("{", "<").Replace("}", ">");

        var type = System.Type.GetType(typeName, false, false);
        if (type == null)
        {
          continue;
        }

        var description = node.InnerXml ?? string.Empty;

        yield return new DocumentedException(type.FullName, description);
      }
    }

    #endregion
  }
}